import copy
import os
import warnings

import pandas as pd


class SchedulingProblem:
    def __init__(self):
        self.operations = pd.Series()  # index starts from 0
        self.machines_operation = pd.Series()
        self.jobs_operation = pd.Series()  # index starts from 0

    @staticmethod
    def load(path, index_automatically_subtracts_one=True):
        raise NotImplementedError

    @classmethod
    def is_valid_file_name(cls, file_name):
        return True

    @classmethod
    def load_all(cls, path):
        problem_list = []
        if path is None:
            return
        isdir = os.path.isdir(path)
        files = []
        if isdir:
            files = os.listdir(path)
            files.sort()
        for file_name in files:
            if not cls.is_valid_file_name(file_name):
                continue
            one_file_path = os.path.join(path, file_name)
            problem_list.append(cls().load(one_file_path))
        return problem_list

    def duplicate_machine(self, old_mid, new_mid):
        if new_mid in self.machines_operation:
            warnings.warn(f"machine {new_mid} has existed")
        if old_mid == -1:
            self.machines_operation[new_mid] = pd.Series([])
        else:
            old_mo = self.machines_operation[old_mid]
            for ok in old_mo:
                self.operations[ok][new_mid] = self.operations[ok][old_mid]
            self.machines_operation[new_mid] = copy.deepcopy(old_mo)
        return self

    def duplicate_job(self, old_jid, new_jid):
        if new_jid in self.machines_operation:
            warnings.warn(f"job {new_jid} has existed")
        if old_jid == -1:
            self.jobs_operation[new_jid] = pd.Series()
        else:
            old_jo = self.jobs_operation[old_jid]
            self.jobs_operation[new_jid] = old_jo
        return self

    def set_operation_and_machine_and_process_time(self, oid, mid, process_time):
        if oid not in self.operations:
            self.operations[oid] = dict()
        self.operations[oid][mid] = process_time
        if oid not in self.machines_operation[mid]:
            self.machines_operation[mid].append(oid)
        return self

    def set_operation_and_job(self, oid, jid):
        if oid not in self.operations:
            self.operations[oid] = dict()
        self.jobs_operation[jid].append(oid)
        return self

    @classmethod
    def duplicate(cls, problem):
        return copy.deepcopy(problem)

    def get_job_id_from_operation_id(self, operation_id):
        for jid in self.jobs_operation:
            jo = self.jobs_operation[jid]
            if operation_id in jo:
                return jid

    def convert_to_file_string(self):
        raise NotImplementedError

    def save(self, path):
        file = open(path, 'w+')
        file.write(self.convert_to_file_string())
        file.close()

    @property
    def total_number_of_jobs(self):
        return len(self.jobs_operation)

    @property
    def total_number_of_machines(self):
        return len(self.machines_operation)

    @property
    def average_number_of_selectable_machines_per_process(self):
        num = 0
        for mo in self.machines_operation:
            num += len(mo)
        num = num / len(self.operations)
        return num

    def processing_time_maximum_normalized(self):
        max_processing_time = -1
        for op_id in self.operations.index:
            max_processing_time = max(max_processing_time, self.operations[op_id].max())
        for op_id in self.operations.index:
            self.operations[op_id] = self.operations[op_id]/max_processing_time